package hikari.gui;

import com.gooddies.events.ValueChangedEvent;
import com.gooddies.persistence.Properties;
import com.gooddies.swing.JpSplitPanel;
import com.gooddies.wiring.Wiring;
import com.gooddies.wiring.annotations.PostWiring;
import com.gooddies.wiring.annotations.Wire;
import com.gooddies.wiring.annotations.WiringComponent;
import hikari.exception.CancelActionException;
import hikari.gui.assets.AssetsTree;
import hikari.gui.guicomponents.HToolbar;
import hikari.gui.guicomponents.JpWindow;
import hikari.gui.guicomponents.MainMenuBar;
import hikari.gui.guicomponents.ProjectsFileChooser;
import hikari.gui.guicomponents.hLabel;
import hikari.gui.panels.Main3dPanel;
import hikari.gui.panels.ModifyPanel;
import hikari.managers.ActionManager;
import hikari.managers.Manager3D;
import hikari.managers.undo.Undo;
import hikari.persistence.SaveManager;
import hikari.preferences.GameProperties;
import hikari.utils.Utils;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.ScrollPaneConstants;
import org.apache.commons.io.FileUtils;

/**
 * @author Dmitry
 */
@WiringComponent(singleton = true)
public class MainWindow extends JpWindow {

    @Wire
    private Main3dPanel panel3d;
    @Wire
    private AssetsTree assetsTree;
    @Wire
    private ModifyPanel modifyPanel;
    private HToolbar navigationPanel;
    private HToolbar mainToolbar;
    private File sceneFile = null;
    private boolean modified = false;
    @Wire
    private Manager3D manager3D;
    @Wire
    private GameProperties gameProperties;
    @Wire
    private Main3dPanelDragDrop main3dPanelDragDrop;
    @Wire
    private Utils utils;
    @Wire
    private Undo undo;

    private MainWindow() {
        initProperties();
    }

    @PostWiring
    private void init() {
        setIconImage(utils.loadImageFromResources("icons/programmIcon.png"));
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);

        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                exit();
            }

            @Override
            public void windowDeactivated(WindowEvent e) {
                if (firstActivated) {
                    manager3D.stopRenderingTimer();
                }
            }

            @Override
            public void windowActivated(WindowEvent e) {
                if (firstActivated) {
                    manager3D.startRenderingTimer();
                }
            }
        });

        createGUI();
        initModifyHandler();
        initUnhandledExceptionsHandler();
    }

    private void initUnhandledExceptionsHandler() {
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                if (!(e instanceof CancelActionException)) {
                    utils.errorMessage(e);
                }
            }
        });
    }

    private void initModifyHandler() {
        ValueChangedEvent.addBeforeEventListener(new ValueChangedEvent.ValueChangedBefore() {
            @Override
            public void fire(JComponent component, Object value) {
                setModified();
            }
        });
    }

    public void setModified() {
        if (modified == false) {
            modified = true;
            super.setTitle(getTitle() + " *");
        }
    }

    public void setClearModified() {
        if (modified == true) {
            modified = false;
            String title = getTitle();
            if (title.endsWith("*")) {
                String newTitle = title.substring(0, title.length() - 1);
                super.setTitle(newTitle);
            }
        }
    }

    public boolean isModified() {
        return modified;
    }

    private void openSavedProjectAfterStart() {
        if (Properties.get().hasKey("project")) {
            String path = Properties.get().getString("project");
            if (new File(path).exists()) {
                openProject(path);
            } else {
                utils.errorMessage("Cannot open project with selected path " + path);
                Properties.get().removeKey("project");
            }
        }
    }

    @Override
    public void setTitle(String title) {
        String projectName = gameProperties.getProjectName();
        String fullTitle = "Hikari - ";
        if (projectName != null && !projectName.equals("")) {
            fullTitle += projectName + " - ";
        }
        if (modified) {
            title += " *";
        }
        super.setTitle(fullTitle + title);
    }

    private void createGUI() {
        setLayout(new BorderLayout());
        createMainMenu();
        JpSplitPanel mainVerticalSplit = new JpSplitPanel("mainVerticalSplit");
        mainVerticalSplit.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
        JPanel modifyNavigationPanel = new JPanel();
        modifyNavigationPanel.setLayout(new BorderLayout());
        modifyNavigationPanel.add(createModifyPanel());
        modifyNavigationPanel.add(createNavigationPanel(), BorderLayout.SOUTH);
        mainVerticalSplit.setRightComponent(modifyNavigationPanel);

        JpSplitPanel mainHorizontalSplit = new JpSplitPanel("mainHorizontalSplit");
        mainHorizontalSplit.setOrientation(JSplitPane.VERTICAL_SPLIT);
        mainVerticalSplit.setTopComponent(mainHorizontalSplit);

        JpSplitPanel navigationSplit = new JpSplitPanel("navigationSplit");
        navigationSplit.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
        navigationSplit.setLeftComponent(createHierarchyPanel());
        navigationSplit.setRightComponent(createAssetsPanel());
        mainHorizontalSplit.setBottomComponent(navigationSplit);
        mainToolbar = createToolbar();
        add(mainToolbar, BorderLayout.NORTH);
        add(mainVerticalSplit);
        main3dPanelDragDrop.initDragDrop(panel3d);
        mainHorizontalSplit.setTopComponent(panel3d);
        init3dEngine();
    }

    public void postStart() {
        newSceneInternal();
    }

    private void addRemoveHandler() {
        panel3d.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_DELETE) {
                    manager3D.removeSelectedNode();
                }
            }
        });
    }
    private boolean firstActivated = false;

    private void init3dEngine() {
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowOpened(WindowEvent e) {
                if (firstActivated == false) {
                    manager3D.setMain3dPanel(panel3d);
                    manager3D.initIrrlichtDevice(panel3d.getHWND());
                    manager3D.addRenderingTimer();
                    openSavedProjectAfterStart();
                    manager3D.startRenderingTimer();
                }
                firstActivated = true;
            }
        });


        addRemoveHandler();
    }

    private HToolbar createNavigationPanel() {
        navigationPanel = Wiring.getComponent(HToolbar.class);
        navigationPanel.setIconSizes(25);
        BufferedImage image = utils.loadImageFromResources("icons/panIcon.png");
        navigationPanel.addButton("Pan 3d view", ActionManager.PAN, image);
        navigationPanel.addButton("Observe 3d view", ActionManager.OBSERVE, utils.loadImageFromResources("icons/observeIcon.png"));
        return navigationPanel;
    }

    private HToolbar createToolbar() {
        HToolbar toolbar = Wiring.getComponent(HToolbar.class);
        toolbar.addSeparator();
        toolbar.addButton("Move selected object", ActionManager.MOVE, utils.loadImageFromResources("icons/moveIcon.png")).selectWithoutAction();
        toolbar.addButton("Rotate selected object", ActionManager.ROTATE, utils.loadImageFromResources("icons/rotateIcon.png"));
        toolbar.addButton("Scale selected object", ActionManager.SCALE, utils.loadImageFromResources("icons/scaleIcon.png"));
        toolbar.addSeparator();
        JButton undoButton = toolbar.addSimpleButton("Undo", ActionManager.UNDO, utils.loadImageFromResources("icons/undo.png"));
        undoButton.setEnabled(false);
        undo.addUndoButton(undoButton);
        return toolbar;
    }

    private void createMainMenu() {
        MainMenuBar mainMenu = Wiring.getComponent(MainMenuBar.class);
        setJMenuBar(mainMenu);
    }

    private JScrollPane createModifyPanel() {
        JScrollPane scroll = new JScrollPane(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        modifyPanel.init(scroll);
        scroll.getViewport().add(modifyPanel);
        scroll.setMinimumSize(new Dimension(210, 100));
        return scroll;
    }

    private JPanel createHierarchyPanel() {
        JPanel hierarchyPanel = new JPanel();
        hierarchyPanel.setLayout(new BorderLayout());
        hierarchyPanel.add(new hLabel("Project Hierarchy"), BorderLayout.NORTH);
        JScrollPane scroll = new JScrollPane(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        hierarchyPanel.add(scroll);
        scroll.getViewport().add((JComponent) Wiring.getComponent("HierarchyTree"));
        return hierarchyPanel;
    }

    private JPanel createAssetsPanel() {
        JPanel assetsPanel = new JPanel();
        assetsPanel.setLayout(new BorderLayout());
        assetsPanel.add(new hLabel("Project Assets"), BorderLayout.NORTH);
        JScrollPane scroll = new JScrollPane(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        assetsPanel.add(scroll);
        scroll.getViewport().add(assetsTree);
        return assetsPanel;
    }

    private void initProperties() {
        Properties.get().readProperties();
    }

    private void saveProperties() {
        Properties.get().saveProperties();
    }

    public void newScene() {
        if (!gameProperties.isProjectSaved()) {
            utils.errorMessage("Please save project");
            return;
        }
        checkNotSavedScene();

        newSceneInternal();
    }

    private void newSceneInternal() {
        manager3D.reset();
        setTitle("Not Saved Scene");
        sceneFile = null;
        setClearModified();
    }

    public boolean isSceneSaved() {
        return sceneFile != null;
    }

    public String getCurrentSceneName() {
        if (isSceneSaved()) {
            return utils.getFileNameWithoutExtension(sceneFile.getAbsolutePath());
        }
        return null;
    }

    public void exit() {
        saveProperties();
        checkNotSavedScene();

        System.exit(0);
    }

    @Override
    public String getWindowName() {
        return "MainWindow";
    }

    public void open() {
        if (!gameProperties.isProjectSaved()) {
            utils.errorMessage("Please save project");
            return;
        }
        checkNotSavedScene();
        SceneChooserDialog dialog = new SceneChooserDialog(this);
        String sceneName = dialog.openScene();
        if (sceneName == null) {
            return;
        }
        open(sceneName);
    }

    private boolean checkNotSavedScene() {
        if (isModified()) {
            Boolean result = utils.confirm(this, "Confirmation", "Scene is not saved. Save it?");
            if (result == null) {
                throw new CancelActionException();
            }
            if (result == true) {
                return save();
            }
        }
        return true;
    }

    private Boolean checkAndAskModified(String askTitle) {
        if (isModified()) {
            Boolean result = utils.confirm(this, "Confirmation", "The scene is not changed. " + askTitle);
            return result;
        }

        return true;
    }

    private void open(String sceneName) {
        if (!gameProperties.isProjectSaved()) {
            utils.errorMessage("Please save project");
            return;
        }

        newSceneInternal();
        SaveManager saveManager = Wiring.getComponent(SaveManager.class);
        sceneFile = SceneChooserDialog.formatSceneFile(sceneName);
        saveManager.openScene(sceneFile);
        setTitle(sceneName);
        setClearModified();
    }

    public boolean save() {
        if (!gameProperties.isProjectSaved()) {
            utils.errorMessage("Please save project");
            return false;
        }

        if (sceneFile == null) {
            saveAs();
            return true;
        }

        SaveManager saveManager = Wiring.getComponent(SaveManager.class);
        File tempFile = null;
        try {
            tempFile = File.createTempFile("hik_temp", "temp");
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
        boolean result = saveManager.saveScene(tempFile);
        if (result) {
            if (sceneFile.exists() && !sceneFile.delete()) {
                utils.errorMessage("Cannot delete previous scene file " + sceneFile.getName() + ".You can try to save the scene by another name");
            }
            try {
                FileUtils.moveFile(tempFile, sceneFile);
            } catch (IOException ex) {
                throw new RuntimeException("Cannot create scene file. You can find copy at this path " + tempFile.getAbsolutePath(), ex);
            }

            setClearModified();
        }

        return result;
    }

    public boolean saveAs() {
        SceneChooserDialog dialog = new SceneChooserDialog(this);
        String sceneName = dialog.saveScene();
        if (sceneName == null) {
            throw new CancelActionException();
        }

        setTitle(sceneName);
        sceneFile = SceneChooserDialog.formatSceneFile(sceneName);
        save();
        return true;
    }

    public void newProject() {
        Boolean res = checkAndAskModified("Are you sure you want to create new project and discard unsaved changes in current scene?");
        if (res == null || res == false) {
            return;
        }

        ProjectsFileChooser projectsFileChooser = Wiring.getComponent(ProjectsFileChooser.class);
        int result = projectsFileChooser.showSaveDialog(this);
        if (result == ProjectsFileChooser.APPROVE_OPTION) {
            try {
                String path = projectsFileChooser.getSelectedPath();
                manager3D.newProject(path);
                openProject(path);
            } catch (IOException ex) {
                ex.printStackTrace();
                utils.errorMessage(this, "Error while creating project");
            }
        }
    }

    public void openProject() {
        Boolean res = checkAndAskModified("Are you sure you want to open another project and discard unsaved changes in current scene?");
        if (res == null || res == false) {
            return;
        }
        ProjectsFileChooser projectsFileChooser = Wiring.getComponent(ProjectsFileChooser.class);
        int result = projectsFileChooser.showOpenDialog(this);
        if (result == ProjectsFileChooser.APPROVE_OPTION) {
            String path = projectsFileChooser.getSelectedPath();
            if (!new File(path).exists()) {
                utils.errorMessage("Folder does not contain file \"game.hikari\"");
            } else {
                openProject(path);
            }
        }
    }

    private void openProject(String path) {
        newSceneInternal();
        try {
            gameProperties.openProject(path);
            assetsTree.refreshTree();
            setTitle("Not saved scene");
            setClearModified();
        } catch (IOException ex) {
            ex.printStackTrace();
            utils.errorMessage(this, "Error while loading project");
        }
    }
}
